import sys
import blue
import service
import util
import random
import types
import time
from service import *
import form
import xtriui
import uix
import uiutil
import uiconst
import uicls
MAX_VOLUME = 1000000
Progress = lambda title, text, current, total: sm.GetService('loading').ProgressWnd(title, text, current, total)

class InvTools(service.Service):
    __module__ = __name__
    __exportedcalls__ = {}
    __notifyevents__ = []
    __dependencies__ = []
    __servicename__ = 'invtools'
    __displayname__ = 'invtools'
    __guid__ = 'svc.invtools'

    def __init__(self):
        Service.__init__(self)
        self.inv = eve.GetInventory



    def Run(self, memStream = None):
        self.state = SERVICE_START_PENDING
        Service.Run(self, memStream)
        self.wnd = None
        self.state = SERVICE_RUNNING



    def Stop(self, memStream = None):
        self.state = SERVICE_STOP_PENDING
        Service.Stop(self, memStream)
        self.state = SERVICE_STOPPED



    def Startup(self, *args):
        return 



    def GetShip(self, *args):
        if not hasattr(eve.session, 'shipid'):
            return 
        else:
            return self.inv(eve.session.shipid)



    def GetSelf(self, *args):
        if not hasattr(eve.session, 'charid'):
            return 
        else:
            return self.inv(eve.session.charid)



    def GetSpecial(self, loc = None, *args):
        if (loc is None):
            return 
        else:
            return self.inv(loc)



    def GetTypes(self, want, categories = True, onlyPublished = True, onlyUnpublished = False, volume = None, *args):
        if (volume is None):
            volume = float(MAX_VOLUME)
        listOfTypes = []
        fieldType = ['groupID', 'categoryID'][categories]
        queryType = ['invtypes', 'invgroups'][categories]
        cfgQuery = getattr(cfg, queryType)
        if onlyPublished:
            onlyUnpublished = False
        for item in cfg.invtypes:
            if onlyPublished:
                if item.published:
                    lookup = [item.typeID, item.groupID]
                    use = lookup[categories]
                    if (getattr(cfgQuery.Get(use), fieldType) in want):
                        if (item.volume < volume):
                            listOfTypes.append(lookup[0])
            elif onlyUnpublished:
                if not item.published:
                    lookup = [item.typeID, item.groupID]
                    use = lookup[categories]
                    if (getattr(cfgQuery.Get(use), fieldType) in want):
                        if (item.volume < volume):
                            listOfTypes.append(lookup[0])
            else:
                lookup = [item.typeID, item.groupID]
                use = lookup[categories]
                if (getattr(cfgQuery.Get(use), fieldType) in want):
                    if (item.volume < volume):
                        listOfTypes.append(lookup[0])

        return listOfTypes



    def SpawnRandom(self, loc = None, flag = None, minQty = None, maxQty = None, iterations = None, categories = True, groups = False, want = None, onlyPublished = True, onlyUnpublished = False, useMultiMove = True, volume = None, *args):
        if (loc is None):
            return 
        if ((loc == eve.session.shipid) and (sm.GetService('godma').GetItem(loc).groupID == const.groupCapsule)):
            eve.Message('CustomNotify', {'notify': 'You cannot spawn items into capsule.'})
            return 
        if (minQty is None):
            minQty = 1
        if (maxQty is None):
            maxQty = 10
        if (iterations is None):
            iterations = 10
        if (minQty > maxQty):
            eve.Message('CustomNotify', {'notify': 'Min stack size is greater than max stack size.'})
            return 
        if (minQty > iterations):
            eve.Message('CustomNotify', {'notify': 'Min stack size is greater than number of items.'})
            return 
        if (categories and groups):
            groups = False
        if not categories:
            groups = True
        if (want is None):
            categories = True
            groups = False
            want = [const.categoryCharge,
             const.categoryCommodity,
             const.categoryModule]
        if (type(want) is types.IntType):
            want = [want]
        if (type(want) is not types.ListType):
            return 
        possibleTypes = []
        selectedTypesAndQty = []
        mustMove = []
        spawntype = ['groupID', 'categoryID'][categories]
        cfgtype = ['invtypes', 'invgroups'][categories]
        cfgquery = getattr(cfg, cfgtype)
        possibleTypes = self.GetTypes(want, categories=categories, onlyPublished=onlyPublished, onlyUnpublished=onlyUnpublished, volume=volume)
        if (len(possibleTypes) == 0):
            return 
        for i in xrange(1, (iterations + 1)):
            selectedTypesAndQty.append((random.choice(possibleTypes), random.choice(xrange(minQty, (maxQty + 1)))))

        title = 'Spawning...'
        idx = int()
        qty = len(selectedTypesAndQty)
        Progress(title, 'Processing', 0, qty)
        for (t, q,) in selectedTypesAndQty:
            idx += 1
            msg = ('Processing %d of %d' % (idx, qty))
            Progress(title, msg, idx, qty)
            if (q == 0):
                q = 1
            if (eve.session.role & service.ROLE_WORLDMOD):
                itemID = sm.RemoteSvc('slash').SlashCmd(('/createitem %d %d' % (t, q)))
            elif (eve.session.role & service.ROLE_GML):
                try:
                    itemID = sm.RemoteSvc('slash').SlashCmd(('/load me %d %d' % (t, q)))[0]
                except UserError:
                    sys.exc_clear()
            else:
                return 
            mustMove.append(itemID)

        Progress(title, 'Complete', 1, 1)
        if (loc != eve.session.shipid):
            self.MoveItems(items=mustMove, source=eve.session.shipid, destination=loc, flag=flag, multi=useMultiMove, suppress=True)



    def GetFlags(self, *args):
        flagList = []
        for key in const.__dict__:
            if key.lower().startswith('flag'):
                flagList.append(key)

        flagList.sort()
        return flagList



    def GetItemFromContainer(self, itemID = None, container = None, *args):
        if (itemID is None):
            return 
        if (container is None):
            return 
        inv = [ item for item in self.inv(container).List() ]
        for item in inv:
            if (item.itemID == itemID):
                return item




    def MoveItems(self, items = None, source = None, destination = None, flag = None, multi = True, suppress = False, **kwargs):
        if (None in (items,
         source,
         destination)):
            return 
        if not suppress:
            destinationCargo = {}
            postDestinationCargo = {}
            sourceCargo = {}
            postSourceCargo = {}
            cargoDict = {}
            for rec in self.inv(destination).List():
                destinationCargo[rec.itemID] = rec.stacksize

            for rec in self.inv(source).List():
                sourceCargo[rec.itemID] = rec.stacksize

            for each in items:
                cargoDict[each] = self.GetItemFromContainer(each, source).stacksize

        title = 'Moving...'
        Progress(title, 'Telling Scotty the docking manager what to do...', 0, 1)
        if multi:
            if (flag is not None):
                self.inv(destination).MultiAdd(items, source, flag=flag, **kwargs)
            else:
                self.inv(destination).MultiAdd(items, source, **kwargs)
        else:
            idx = int()
            all = len(items)
            for itemID in items:
                idx += 1
                msg = ('Processing %d of %d' % (idx, all))
                Progress(title, msg, idx, all)
                self.inv(destination).Add(itemID, source, **kwargs)

        Progress(title, "All done, don't forget to pay the man!", 1, 1)
        if not suppress:
            blue.pyos.synchro.Sleep(5000)
            for rec in self.inv(destination).List():
                postDestinationCargo[rec.itemID] = rec.stacksize

            for rec in self.inv(source).List():
                postSourceCargo[rec.itemID] = rec.stacksize

            for k in cargoDict.iterkeys():
                if (k in postDestinationCargo):
                    qtyCargo = cargoDict[k]
                    qtyDest = postDestinationCargo[k]
                    if (qtyCargo == qtyDest):
                        pass
                    else:
                        print k,
                        print ' is in destination and qty is different!'
                else:
                    print '\t%s is not in destination',
                    print k
                    print '\tsrc inv rec',
                    print self.GetItemFromContainer(k, source)
                    print '\tdest inv rec:',
                    print self.GetItemFromContainer(k, destination)

            postSourceCopy = postSourceCargo.copy()
            postSourceCopy.update(cargoDict)
            print '\tsrc ok?\t',
            print ['No', 'Yes'][(postSourceCopy == sourceCargo)]
            destinationCopy = destinationCargo.copy()
            destinationCopy.update(cargoDict)
            print '\tdest ok?\t',
            print ['No', 'Yes'][(destinationCopy == postDestinationCargo)]



    def InvMoveLoop(self, loop = None, noSpawn = True, *args):
        if (loop is None):
            loop = 10
        _loop = loop
        if not noSpawn:
            self.SpawnRandom(eve.session.shipid, iterations=10)
        items = self.GetShip().ListCargo()
        if (len(items) == 0):
            return 
        itemIDs = [ rec.itemID for rec in items ]
        moveCommands = [(eve.session.shipid,
          const.containerHangar,
          const.flagHangar,
          False),
         (const.containerHangar,
          eve.session.shipid,
          const.flagCargo,
          False),
         (eve.session.shipid,
          const.containerHangar,
          const.flagHangar,
          True),
         (const.containerHangar,
          eve.session.shipid,
          const.flagCargo,
          True)]
        lookup = {eve.session.shipid: 'Ship',
         const.containerHangar: 'Hangar Floor',
         const.flagCargo: 'Cargo Bay',
         const.flagHangar: 'Corp Hangar, Division 1',
         const.flagCorpSAG2: 'Corp Hangar, Division 2',
         const.flagCorpSAG3: 'Corp Hangar, Division 3',
         const.flagCorpSAG4: 'Corp Hangar, Division 4',
         const.flagCorpSAG5: 'Corp Hangar, Division 5',
         const.flagCorpSAG6: 'Corp Hangar, Division 6',
         const.flagCorpSAG7: 'Corp Hangar, Division 7'}
        usecase = {True: 'multimove',
         False: 'single move'}
        office = sm.StartService('corp').GetOffice()
        if (office is not None):
            office = office.itemID
        if (office is not None):
            moveCommands.extend([(eve.session.shipid,
              office,
              const.flagHangar,
              False),
             (office,
              office,
              const.flagCorpSAG2,
              False),
             (office,
              office,
              const.flagCorpSAG3,
              False),
             (office,
              office,
              const.flagCorpSAG4,
              False),
             (office,
              office,
              const.flagCorpSAG5,
              False),
             (office,
              office,
              const.flagCorpSAG6,
              False),
             (office,
              office,
              const.flagCorpSAG7,
              False),
             (office,
              eve.session.shipid,
              const.flagCargo,
              False),
             (eve.session.shipid,
              office,
              const.flagHangar,
              True),
             (office,
              office,
              const.flagCorpSAG2,
              True),
             (office,
              office,
              const.flagCorpSAG3,
              True),
             (office,
              office,
              const.flagCorpSAG4,
              True),
             (office,
              office,
              const.flagCorpSAG5,
              True),
             (office,
              office,
              const.flagCorpSAG6,
              True),
             (office,
              office,
              const.flagCorpSAG7,
              True),
             (office,
              eve.session.shipid,
              const.flagCargo,
              True)])
            lookup[office] = 'Office'
        start = time.time()
        while loop:
            for (src, dest, f, m,) in moveCommands:
                if (f is not None):
                    print ("Moving from %s to %s's %s using %s:" % (lookup[src],
                     lookup[dest],
                     lookup[f],
                     usecase[m]))
                else:
                    print ('Moving from %s to %s using %s:' % (lookup[src],
                     lookup[dest],
                     usecase[m]))
                t = time.time()
                self.MoveItems(itemIDs, src, dest, f, m)
                duration = (time.time() - t)
                print ('Move took %d:%.2d to move %s items\n' % ((duration / 60),
                 (duration % 60),
                 len(items)))

            loop -= 1

        finish = (time.time() - start)
        print ('Move test complete, total time: %d:%.2d for %s iteration(s)' % ((finish / 60),
         (finish % 60),
         _loop))
        print ('%s items were moved %s times' % (len(items), (len(moveCommands) * _loop)))




class InvToolsWnd(uicls.Window):
    __guid__ = 'form.invTools'
    __neocommenuitem__ = (('Inventory Tools', 'invTools'),
     True,
     ROLE_GMH)
    __notifyevents__ = ['OnSessionChanged']
    RANDOM_COMBO = ('Random', 'random')
    PUBLISHED_LIST = ['<color=0xffff0000>Not Published<color=0xffffffff>', '<color=0xff00ff00>Published<color=0xffffffff>']

    def ApplyAttributes(self, attributes):
        uicls.Window.ApplyAttributes(self, attributes)
        self.SetMinSize([200, 290])
        self.SetWndIcon(None)
        self.SetCaption('Inventory Tools')
        self.MakeUnResizeable()
        self.SetTopparentHeight(0)
        self.cats = None
        self.grps = None
        self.locs = None
        self.DoSetup()
        self.Begin()



    def OnSessionChanged(self, isRemote, session, change):
        self.location.entries = self.locs = self.GetLocations()
        self.location.SetValue('ship')



    def DoSetup(self):
        self.cats = [ (('%s [%s]' % (c.name, self.PUBLISHED_LIST[c.published])), c.categoryID) for c in cfg.invcategories if (c.categoryID is not 0) ]
        self.cats.sort()
        self.cats.insert(0, self.RANDOM_COMBO)
        self.grps = [self.RANDOM_COMBO]
        self.default = {'categoryID': 'random',
         'groupID': 'random',
         'published': 1,
         'multimove': 1,
         'minQty': 1,
         'maxQty': 10,
         'number': 10,
         'volume': MAX_VOLUME,
         'location': 'ship'}
        self.config = self.default.copy()
        self.locs = self.GetLocations()
        self.spawnlocation = eve.session.shipid



    def Begin(self):
        margin = const.defaultPadding
        textalign = (margin + 3)
        cmLblLeft = 55
        cmPadLeft = (cmLblLeft + (margin * 2))
        cmPadRight = margin
        self.main = main = uicls.Container(name='main', parent=self.sr.main, pos=(margin,
         margin,
         margin,
         margin))
        uicls.Frame(parent=main, color=(1.0, 1.0, 1.0, 0.2))
        uix.GetContainerHeader('Random Item Spawn', main, bothlines=0)
        self.categories = uicls.Combo(label='Category', labelleft=cmLblLeft, parent=main, options=self.cats, select=self.config['categoryID'], name='category', align=uiconst.TOTOP, callback=self.DoCategoryChange, pos=(0,
         margin,
         0,
         0))
        self.categories.padLeft = cmPadLeft
        self.categories.padRight = cmPadRight
        self.groups = uicls.Combo(label='Group', labelleft=cmLblLeft, parent=main, options=self.grps, select=self.config['groupID'], name='group', align=uiconst.TOTOP, callback=self.DoGroupChange, pos=(0,
         margin,
         0,
         0))
        self.groups.padLeft = cmPadLeft
        self.groups.padRight = cmPadRight
        self.pub = uicls.Checkbox(text='use only published items', parent=main, configName='pub', retval=0, checked=bool((self.config['published'] == 1)), groupname='published', align=uiconst.TOTOP, callback=self.DoPublishChange, pos=(margin,
         margin,
         0,
         0))
        self.unpub = uicls.Checkbox(text='use only unpublished items', parent=main, configName='unpub', retval=0, checked=bool((self.config['published'] == 0)), groupname='published', align=uiconst.TOTOP, callback=self.DoPublishChange, pos=(margin,
         0,
         0,
         0))
        self.all = uicls.Checkbox(text='use all items', parent=main, configName='all', retval=0, checked=bool((self.config['published'] == -1)), groupname='published', align=uiconst.TOTOP, callback=self.DoPublishChange, pos=(margin,
         0,
         0,
         0))
        self.multimove = uicls.Checkbox(text='use multimove', parent=main, configName='multimove', retval=0, checked=self.config['multimove'], groupname=None, align=uiconst.TOTOP, callback=self.DoMoveChange, pos=(margin,
         0,
         0,
         0))
        for checkbox in [self.pub,
         self.unpub,
         self.all,
         self.multimove]:
            checkbox.hint = 'Changing this is <color=0xffff0000>not advised<color=0xffffffff>.<br>Proceed with caution!'
            text = uiutil.GetChild(checkbox, 'text')
            text.left += margin

        uicls.Line(parent=self.pub, color=(1.0, 1.0, 1.0, 0.2), align=uiconst.TOTOP)
        uicls.Line(parent=self.all, color=(1.0, 1.0, 1.0, 0.2), align=uiconst.TOBOTTOM)
        uicls.Line(parent=self.multimove, color=(1.0, 1.0, 1.0, 0.2), align=uiconst.TOBOTTOM)
        par = uicls.Container(parent=main, align=uiconst.TOTOP, height=16, top=margin)
        txt = uicls.Label(text='Minimum stack size', name='txtMin', parent=par, align=uiconst.TOLEFT, height=12, top=5, left=textalign, autoheight=False, fontsize=10, letterspace=1, linespace=9, uppercase=1, state=uiconst.UI_NORMAL)
        txt.rectTop = -2
        self.min = uicls.SinglelineEdit(name='minQty', parent=par, setvalue=str(self.config['minQty']), left=margin, width=70, height=20, align=uiconst.TORIGHT, ints=(1, 100))
        par = uicls.Container(parent=main, align=uiconst.TOTOP, height=16, top=margin)
        txt = uicls.Label(text='Maximum stack size', name='txtMax', parent=par, align=uiconst.TOLEFT, height=12, top=5, left=textalign, autoheight=False, fontsize=10, letterspace=1, linespace=9, uppercase=1, state=uiconst.UI_NORMAL)
        txt.rectTop = -2
        self.max = uicls.SinglelineEdit(name='maxQty', parent=par, setvalue=str(self.config['maxQty']), left=margin, width=70, height=20, align=uiconst.TORIGHT, ints=(1, 1000))
        par = uicls.Container(parent=main, align=uiconst.TOTOP, height=16, top=margin)
        txt = uicls.Label(text='Number of Items', name='txtQty', parent=par, align=uiconst.TOLEFT, height=12, top=5, left=textalign, autoheight=False, fontsize=10, letterspace=1, linespace=9, uppercase=1, state=uiconst.UI_NORMAL)
        txt.rectTop = -2
        self.number = uicls.SinglelineEdit(name='number', parent=par, setvalue=str(self.config['number']), left=margin, width=70, height=20, align=uiconst.TORIGHT, ints=(1, 1000))
        par = uicls.Container(parent=main, align=uiconst.TOTOP, height=16, top=margin)
        txt = uicls.Label(text='Maximum item volume', name='txtVol', parent=par, align=uiconst.TOLEFT, height=12, top=5, left=textalign, autoheight=False, fontsize=10, letterspace=1, linespace=9, uppercase=1, state=uiconst.UI_NORMAL)
        txt.rectTop = -2
        self.volume = uicls.SinglelineEdit(name='volume', parent=par, setvalue=str(self.config['volume']), left=margin, width=70, height=20, align=uiconst.TORIGHT, floats=(0, MAX_VOLUME))
        self.location = uicls.Combo(label='Location', labelleft=cmLblLeft, parent=main, options=self.locs, select=self.config['location'], name='location', align=uiconst.TOTOP, callback=self.DoLocationChange, pos=(0,
         margin,
         0,
         0))
        self.location.padLeft = cmPadLeft
        self.location.padRight = cmPadRight
        buttons = [['Spawn',
          self.DoFormSubmit,
          None,
          81]]
        btns = uix.MakeBtnGroup(buttons, line=1, where=main)
        btns.top = margin
        btns.height = 24
        btns.align = uix.UI_ALTOP
        btn = uiutil.GetChild(btns, 'Spawn_Btn')
        if (eve.session.role & service.ROLE_WORLDMOD):
            btn.hint = 'Spawn using /createitem...'
        elif (eve.session.role & service.ROLE_GML):
            btn.hint = 'Spawn using /load...'
        else:
            btn.hint = 'No <b>WORLDMOD</b> or <b>GML</b> role present.'



    def DoCategoryChange(self, combo, text, value):
        self.config['categoryID'] = value
        if (value == 'random'):
            self.grps = [self.RANDOM_COMBO]
        else:
            self.grps = [ (('%s [%s]' % (g.name, self.PUBLISHED_LIST[g.published])), g.groupID) for g in cfg.invgroups if (g.categoryID == value) ]
            self.grps.sort()
            self.grps.insert(0, self.RANDOM_COMBO)
        self.groups.SetValue('random')
        self.groups.entries = self.grps



    def DoGroupChange(self, combo, text, value):
        self.config['groupID'] = value



    def DoLocationChange(self, combo, text, value):
        officeFlags = [const.flagHangar,
         const.flagCorpSAG2,
         const.flagCorpSAG3,
         const.flagCorpSAG4,
         const.flagCorpSAG5,
         const.flagCorpSAG6,
         const.flagCorpSAG7]
        self.config['location'] = value
        if (value in officeFlags):
            self.spawnlocation = (self.office, value)
        elif (value == 'ship'):
            self.spawnlocation = eve.session.shipid
        elif (value == 'hangar'):
            self.spawnlocation = const.containerHangar



    def DoPublishChange(self, cb):
        lookup = {'pub': 1,
         'unpub': 0,
         'all': -1}
        self.config['published'] = lookup[cb.name]



    def DoMoveChange(self, cb):
        self.config['multimove'] = cb.GetValue()



    def DoFormSubmit(self, *args):
        if (eve.session.shipid is None):
            header = 'No ship detected!'
            warning = 'Please ensure you are in a ship before attempting to spawn items.'
            eve.Message('CustomWarning', {'header': header,
             'warning': warning})
            return 
        for formField in [self.min,
         self.max,
         self.number,
         self.volume]:
            configFieldName = formField.name
            self.config[configFieldName] = formField.GetValue()

        loc = self.spawnlocation
        if isinstance(loc, types.TupleType):
            (loc, flag,) = loc
        else:
            flag = None
        if (self.config['categoryID'] == 'random'):
            ids = set([ c.categoryID for c in cfg.invgroups ])
            want = list(ids)
            categories = True
            groups = False
        elif (self.config['groupID'] == 'random'):
            ids = set([ g.groupID for g in cfg.invgroups if (g.categoryID == self.config['categoryID']) ])
            want = list(ids)
            categories = False
            groups = True
        else:
            want = [self.config['groupID']]
            categories = False
            groups = True
        publishedFlags = {0: (False, True),
         1: (True, False),
         -1: (False, False)}
        (onlyPublished, onlyUnpublished,) = publishedFlags[self.config['published']]
        sm.StartService('invtools').SpawnRandom(loc=loc, flag=flag, minQty=self.config['minQty'], maxQty=self.config['maxQty'], iterations=self.config['number'], categories=categories, groups=groups, want=want, onlyPublished=onlyPublished, onlyUnpublished=onlyUnpublished, useMultiMove=self.config['multimove'], volume=self.config['volume'])



    def GetLocations(self):
        ret = [('Current Ship', 'ship')]
        if (eve.session.stationid is not None):
            ret.append(('Hangar Floor', 'hangar'))
            office = sm.StartService('corp').GetOffice()
            if (office is not None):
                office = office.itemID
            if (office is not None):
                self.office = office
                for (flag, text,) in [(const.flagHangar, 'Corp Hangar, Division 1'),
                 (const.flagCorpSAG2, 'Corp Hangar, Division 2'),
                 (const.flagCorpSAG3, 'Corp Hangar, Division 3'),
                 (const.flagCorpSAG4, 'Corp Hangar, Division 4'),
                 (const.flagCorpSAG5, 'Corp Hangar, Division 5'),
                 (const.flagCorpSAG6, 'Corp Hangar, Division 6'),
                 (const.flagCorpSAG7, 'Corp Hangar, Division 7')]:
                    ret.append((text, flag))

        else:
            self.office = None
        return ret




